package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.PointsBoard;
import com.tianji.learning.domain.query.PointsBoardQuery;
import com.tianji.learning.domain.vo.PointsBoardItemVO;
import com.tianji.learning.domain.vo.PointsBoardVO;
import com.tianji.learning.mapper.PointsBoardMapper;
import com.tianji.learning.service.IPointsBoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.tianji.learning.constants.LearningConstants.POINTS_BOARD_TABLE_PREFIX;

/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 * @author yangyu
 * @since 2023-07-29
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {

    private final StringRedisTemplate redisTemplate;

    private final UserClient userClient;

    /**
     * 分页查询学霸积分榜
     *
     * @param query 积分排行榜分页查询条件：赛季id
     * @return 积分排行榜
     */
    @Override
    public PointsBoardVO queryPointsBoardBySeason(PointsBoardQuery query) {
        log.info("分页查询指定赛季的积分排行榜:{}", query);
        //1.获取赛季id，判断是查询当前赛季，还是历史赛季榜单
        Long season = query.getSeason();
        boolean isCurrent = season == null || season == 0;

        //2.拼接Redis的key
        LocalDateTime now = LocalDateTime.now();
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX +
                now.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);

        //3.查询我的积分和排名
        //isCurrent：true：查询当前赛季       false：查询历史榜单
        PointsBoard myBoard = isCurrent ? queryMyCurrentBoard(key) : queryMyHistoryBoard(season);

        //4.查询赛季榜单列表
        List<PointsBoard> boardList = isCurrent ?
                queryCurrentBoardList(key, query.getPageNo(), query.getPageSize()) :
                queryHistoryBoardList(query);

        //4.封装VO
        //积分榜单汇总信息
        PointsBoardVO pointsBoardVO = new PointsBoardVO();

        //4.1.处理我的信息
        if (myBoard != null) {
            pointsBoardVO.setPoints(myBoard.getPoints());
            pointsBoardVO.setRank(myBoard.getRank());
        }

        //赛季榜单列表为空，直接返回VO
        if (CollUtils.isEmpty(boardList)) {
            return pointsBoardVO;
        }

        //4.2.处理榜单列表
        //根据所有用户id，查询榜单列表中的所有用户信息
        Set<Long> uIds = boardList.stream().map(PointsBoard::getUserId).collect(Collectors.toSet());
        List<UserDTO> users = userClient.queryUserByIds(uIds);

        //封装用户信息：用户id和用户名称
        Map<Long, String> userMap = new HashMap<>(uIds.size());
        if (CollUtils.isNotEmpty(users)) {
            //key：用户id  value：用户名称
            userMap = users.stream().collect(Collectors.toMap(UserDTO::getId, UserDTO::getUsername));
        }

        //4.3.转换VO
        //处理积分榜单信息
        ArrayList<PointsBoardItemVO> items = new ArrayList<>(boardList.size());
        for (PointsBoard pointsBoard : boardList) {
            PointsBoardItemVO itemVO = new PointsBoardItemVO();
            //每个用户的积分
            itemVO.setPoints(pointsBoard.getPoints());
            //排名
            itemVO.setRank(pointsBoard.getRank());
            //用户名称
            itemVO.setName(userMap.get(pointsBoard.getUserId()));
            items.add(itemVO);
        }

        //积分榜单汇总信息，设置前100名上榜人信息
        pointsBoardVO.setBoardList(items);
        return pointsBoardVO;
    }


    /**
     * 创建表：新赛季，历史榜单表
     *
     * @param season 上个月的赛季id
     */
    @Override
    public void createPointsBoardTableBySeason(Integer season) {
        //getBaseMapper()：获取mapper对象
        //拼接表名称，创建表     points_board_ + season
        getBaseMapper().createPointsBoardTable(POINTS_BOARD_TABLE_PREFIX + season);
    }


    //查询历史榜单列表
    private List<PointsBoard> queryHistoryBoardList(PointsBoardQuery query) {
        return null;
    }


    //查询本月赛季榜单列表
    @Override
    public List<PointsBoard> queryCurrentBoardList(String key, Integer pageNo, Integer pageSize) {
        //1.计算分页
        //分页查询取决于分页参数，如果第一页开始，每页10条。那么就是0到9
        //start参数：起始索引from = (起始页码 - 1 )*每页条数
        int from = (pageNo - 1) * pageSize;

        //end参数：每页显示条数 = (起始索引 + 每页条数-1)
        int end = from + pageSize - 1;

        // 2.查询
        //第1页:0 9
        //第2页: 10 19
        //第3页3: 20 29
        //tuples：双享数据，key：value格式
        Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet()
                //reverseRangeWithScores：反向获取指定范围内的value和score，并且降序
                //从第0个（第一个）开始，获取多少个
                .reverseRangeWithScores(key, from, end);

        if (CollUtils.isEmpty(tuples)) {
            //数据为空，直接返回空集合
            return CollUtils.emptyList();
        }

        //3.封装
        //定义每个排名    from就是Redis中第一条数据的索引0。加上1就等于1
        int rank = from + 1;

        //new ArrayList<>(tuples.size())：减少集合底层计算扩容
        //定义每条数据的排名：
        ArrayList<PointsBoard> list = new ArrayList<>(tuples.size());
        for (ZSetOperations.TypedTuple<String> tuple : tuples) {
            String userId = tuple.getValue();
            Double points = tuple.getScore();
            if (userId == null || points == null) {
                continue;
            }
            PointsBoard p = new PointsBoard();
            //用户id
            p.setUserId(Long.valueOf(userId));
            //用户积分排名
            p.setRank(rank++);
            list.add(p);
        }
        return list;
    }


    //查询历史榜单，我的积分和排名
    private PointsBoard queryMyHistoryBoard(Long season) {
        //1.获取登录用户
        Long userId = UserContext.getUser();

        //2.计算表名


        //3.查询数据
        return null;
    }


    //从Redis中查询本赛季，我的积分和排名
    private PointsBoard queryMyCurrentBoard(String key) {
        //1.获取登录用户id
        Long userId = UserContext.getUser();

        //2.反转查询某个用户的排名
        Long rank = redisTemplate.opsForZSet().reverseRank(key, userId);

        //3.根据key，Value查询某个用户的积分
        Double points = redisTemplate.opsForZSet().score(key, userId);

        //4.封装返回
        PointsBoard pointsBoard = new PointsBoard();
        //设置我的积分
        pointsBoard.setPoints(points == null ? 0 : points.intValue());
        //排名在Redis中是从0开始，所以需要+1
        pointsBoard.setRank(rank == null ? 0 : rank.intValue() + 1);
        return pointsBoard;
    }
}
